﻿using System;
using System.Collections.Generic;

namespace Glue.Internals
{
    internal class MemberContainerWrapperForRelationContainer
    {
        private readonly MemberContainer members = MemberContainer.CreateContainer();
        private readonly List<IRelation> flattenedtoLeftRelations = new List<IRelation>();
        private readonly List<IRelation> flattenedtoRightRelations = new List<IRelation>();
        private readonly IHashSet<String> leftSideNestedProperty;
        private readonly IHashSet<String> rightSideNestedProperty;

        private readonly IHashSet<String> leftSideSimplifiedNestedProperty;
        private readonly IHashSet<String> rightSideSimplifiedNestedProperty;

        private MemberContainerWrapperForRelationContainer(IHashSet<string> leftSideNestedProperty,
            IHashSet<string> rightSideNestedProperty,
            IHashSet<string> leftSideSimplifiedNestedProperty,
            IHashSet<string> rightSideSimplifiedNestedProperty)
        {
            this.leftSideNestedProperty = leftSideNestedProperty;
            this.rightSideNestedProperty = rightSideNestedProperty;
            this.leftSideSimplifiedNestedProperty = leftSideSimplifiedNestedProperty;
            this.rightSideSimplifiedNestedProperty = rightSideSimplifiedNestedProperty;
        }

        public static MemberContainerWrapperForRelationContainer CreateContainer()
        {
#if SILVERLIGHT 
            return new PropertyContainerWrapperForRelationContainer(new HashSetSilverlight<string>(), 
                new HashSetSilverlight<string>(), 
                new HashSetSilverlight<string>(), 
                new HashSetSilverlight<string>());
#else
            return new MemberContainerWrapperForRelationContainer(
                new HashSetImpl<string>(),
                new HashSetImpl<string>(),
                new HashSetImpl<string>(),
                new HashSetImpl<string>());
#endif
        }

        public bool IsRelatedTowardsOnLeftSide(IObjectMember member)
        {
            return members.ContainsLeftSide(member) || 
                DoesPropertyExistInFlattenedRelation(flattenedtoLeftRelations, member,DoesPropertyExistInFlattenedRelationOnLeftSide);
        }

        public bool IsRelatedTowardsOnRightSide(IObjectMember member)
        {
            return members.ContainsRightSide(member) ||
                DoesPropertyExistInFlattenedRelation(flattenedtoRightRelations, member, DoesPropertyExistInFlattenedRelationOnRightSide);
        }

        public void Add(IRelation relation)
        {
            if (relation.HasTowardsLeft())
            {
                if (IsFlattened(relation.LeftMember))
                    flattenedtoLeftRelations.Add(relation);
                else
                {
                    var nestedLeftPropertyMaybe = relation.LeftMember.GetParentProperty();
                    if (nestedLeftPropertyMaybe.HasValue())
                    {
                        var nestedLeftProperty = nestedLeftPropertyMaybe.GetValue();
                        members.AddToLeftSide(nestedLeftProperty.BaseMemberName);
                        leftSideSimplifiedNestedProperty.Add(nestedLeftProperty.BaseMemberName);
                    }   
                    else if (relation.LeftMember is INestedMember)
                        leftSideNestedProperty.Add(relation.LeftMember.Name);

                    members.AddToLeftSide(relation.LeftMember);
                }
            }
            if (relation.HasTowardsRight())
            {
                if (IsFlattened(relation.RightMember))
                    flattenedtoRightRelations.Add(relation);
                else
                {
                    var nestedRightPropertyMaybe = relation.RightMember.GetParentProperty();
                    if (nestedRightPropertyMaybe.HasValue())
                    {
                        var nestedRightProperty = nestedRightPropertyMaybe.GetValue();
                        members.AddToRightSide(nestedRightProperty.BaseMemberName);
                        rightSideSimplifiedNestedProperty.Add(nestedRightProperty.BaseMemberName);
                    }
                    else if (relation.RightMember is INestedMember)
                        rightSideNestedProperty.Add(relation.RightMember.Name);

                    members.AddToRightSide(relation.RightMember);
                }       
            }   
        }

        private bool IsFlattened(IObjectMember member)
        {
            return member is IRepresentBaseObject;
        }

        public void Remove(IRelation relation)
        {
            if (relation.Direction.Equals(RelationDirection.TwoWays) || relation.Direction.Equals(RelationDirection.TowardsLeft))
                members.RemoveFromLeftSide(relation.LeftMember);
            if (relation.Direction.Equals(RelationDirection.TwoWays) || relation.Direction.Equals(RelationDirection.TowardsRight))
                members.RemoveFromRightSide(relation.RightMember);
        }

        private bool DoesPropertyExistInFlattenedRelation(List<IRelation> flattenedRelations,IObjectMember member, Func<IRelation,IObjectMember,bool> doesExist)
        {
            foreach (var relation in flattenedRelations)
            {
                if (doesExist(relation, member))
                    return true;
            }
            return false;
        }

        private bool DoesPropertyExistInFlattenedRelationOnLeftSide(IRelation relation,IObjectMember member)
        {
            if (!(relation.LeftMember is MemberRepresentingBaseObject))
                return false;
            var converter = (IHoldNestedRelation)relation.GetConverter();
            return converter.IsRelatedTowardsOnLeftSide(member);
        }

        private bool DoesPropertyExistInFlattenedRelationOnRightSide(IRelation relation, IObjectMember member)
        {
            if (!(relation.RightMember is MemberRepresentingBaseObject))
                return false;
            var converter = (IHoldNestedRelation)relation.GetConverter();
            return converter.IsRelatedTowardsOnRightSide(member);
        }

        public bool IsRelatedTowardsOnLeftSideWithNestedMapping(string memberName)
        {
            return leftSideNestedProperty.Contains(memberName);
        }

        public bool IsRelatedTowardsOnRightSideWithNestedMapping(string memberName)
        {
            return rightSideNestedProperty.Contains(memberName);
        }

        public bool IsRelatedTowardsOnLeftSideWithSimplifiedNestedMapping(IObjectMember member)
        {
            return leftSideSimplifiedNestedProperty.Contains(member.Name);
        }

        public bool IsRelatedTowardsOnRightSideWithSimplifiedNestedMapping(IObjectMember member)
        {
            return rightSideSimplifiedNestedProperty.Contains(member.Name);
        }
    }
}
